<?php

/**
 * Class EccLevel
 *
 * @created      19.11.2020
 *
 * @author       smiley <smiley@chillerlan.net>
 * @copyright    2020 smiley
 * @license      MIT
 */
declare(strict_types=1);

namespace zxf\QrCode\Common;

use zxf\QrCode\QRCodeException;

use function array_column;

/**
 * This class encapsulates the four error correction levels defined by the QR code standard.
 */
final class EccLevel
{
    // ISO/IEC 18004:2000 Tables 12, 25

    /** @var int */
    public const L = 0b01; // 7%.

    /** @var int */
    public const M = 0b00; // 15%.

    /** @var int */
    public const Q = 0b11; // 25%.

    /** @var int */
    public const H = 0b10; // 30%.

    /**
     * ISO/IEC 18004:2000 Tables 7-11 - Number of symbol characters and input data capacity for versions 1 to 40
     *
     * @var int[][]
     */
    private const MAX_BITS = [
        //	[    L,     M,     Q,     H]  // v  => modules
        [0,     0,     0,     0], // 0  =>  will be ignored, index starts at 1
        [152,   128,   104,    72], // 1  =>  21
        [272,   224,   176,   128], // 2  =>  25
        [440,   352,   272,   208], // 3  =>  29
        [640,   512,   384,   288], // 4  =>  33
        [864,   688,   496,   368], // 5  =>  37
        [1088,   864,   608,   480], // 6  =>  41
        [1248,   992,   704,   528], // 7  =>  45
        [1552,  1232,   880,   688], // 8  =>  49
        [1856,  1456,  1056,   800], // 9  =>  53
        [2192,  1728,  1232,   976], // 10 =>  57
        [2592,  2032,  1440,  1120], // 11 =>  61
        [2960,  2320,  1648,  1264], // 12 =>  65
        [3424,  2672,  1952,  1440], // 13 =>  69 NICE!
        [3688,  2920,  2088,  1576], // 14 =>  73
        [4184,  3320,  2360,  1784], // 15 =>  77
        [4712,  3624,  2600,  2024], // 16 =>  81
        [5176,  4056,  2936,  2264], // 17 =>  85
        [5768,  4504,  3176,  2504], // 18 =>  89
        [6360,  5016,  3560,  2728], // 19 =>  93
        [6888,  5352,  3880,  3080], // 20 =>  97
        [7456,  5712,  4096,  3248], // 21 => 101
        [8048,  6256,  4544,  3536], // 22 => 105
        [8752,  6880,  4912,  3712], // 23 => 109
        [9392,  7312,  5312,  4112], // 24 => 113
        [10208,  8000,  5744,  4304], // 25 => 117
        [10960,  8496,  6032,  4768], // 26 => 121
        [11744,  9024,  6464,  5024], // 27 => 125
        [12248,  9544,  6968,  5288], // 28 => 129
        [13048, 10136,  7288,  5608], // 29 => 133
        [13880, 10984,  7880,  5960], // 30 => 137
        [14744, 11640,  8264,  6344], // 31 => 141
        [15640, 12328,  8920,  6760], // 32 => 145
        [16568, 13048,  9368,  7208], // 33 => 149
        [17528, 13800,  9848,  7688], // 34 => 153
        [18448, 14496, 10288,  7888], // 35 => 157
        [19472, 15312, 10832,  8432], // 36 => 161
        [20528, 15936, 11408,  8768], // 37 => 165
        [21616, 16816, 12016,  9136], // 38 => 169
        [22496, 17728, 12656,  9776], // 39 => 173
        [23648, 18672, 13328, 10208], // 40 => 177
    ];

    /**
     * ISO/IEC 18004:2000 Section 8.9 - Format Information
     *
     * ECC level -> mask pattern
     *
     * @var int[][]
     */
    private const FORMAT_PATTERN = [
        [ // L
            0b111011111000100,
            0b111001011110011,
            0b111110110101010,
            0b111100010011101,
            0b110011000101111,
            0b110001100011000,
            0b110110001000001,
            0b110100101110110,
        ],
        [ // M
            0b101010000010010,
            0b101000100100101,
            0b101111001111100,
            0b101101101001011,
            0b100010111111001,
            0b100000011001110,
            0b100111110010111,
            0b100101010100000,
        ],
        [ // Q
            0b011010101011111,
            0b011000001101000,
            0b011111100110001,
            0b011101000000110,
            0b010010010110100,
            0b010000110000011,
            0b010111011011010,
            0b010101111101101,
        ],
        [ // H
            0b001011010001001,
            0b001001110111110,
            0b001110011100111,
            0b001100111010000,
            0b000011101100010,
            0b000001001010101,
            0b000110100001100,
            0b000100000111011,
        ],
    ];

    /**
     * The current ECC level value
     *
     * L: 0b01
     * M: 0b00
     * Q: 0b11
     * H: 0b10
     */
    private int $eccLevel;

    /**
     * @param  int  $eccLevel  containing the two bits encoding a QR Code's error correction level
     *
     * @throws \zxf\QrCode\QRCodeException
     */
    public function __construct(int $eccLevel)
    {

        if ((0b11 & $eccLevel) !== $eccLevel) {
            throw new QRCodeException('invalid ECC level');
        }

        $this->eccLevel = $eccLevel;
    }

    /**
     * returns the string representation of the current ECC level
     */
    public function __toString(): string
    {
        return [
            self::L => 'L',
            self::M => 'M',
            self::Q => 'Q',
            self::H => 'H',
        ][$this->eccLevel];
    }

    /**
     * returns the current ECC level
     */
    public function getLevel(): int
    {
        return $this->eccLevel;
    }

    /**
     * returns the ordinal value of the current ECC level
     *
     * references to the keys of the following tables:
     *
     * @see \zxf\QrCode\Common\EccLevel::MAX_BITS
     * @see \zxf\QrCode\Common\EccLevel::FORMAT_PATTERN
     * @see \zxf\QrCode\Common\Version::RSBLOCKS
     */
    public function getOrdinal(): int
    {
        return [
            self::L => 0,
            self::M => 1,
            self::Q => 2,
            self::H => 3,
        ][$this->eccLevel];
    }

    /**
     * returns the format pattern for the given $eccLevel and $maskPattern
     */
    public function getformatPattern(MaskPattern $maskPattern): int
    {
        return self::FORMAT_PATTERN[$this->getOrdinal()][$maskPattern->getPattern()];
    }

    /**
     * returns an array with the max bit lengths for version 1-40 and the current ECC level
     *
     * @return int[]
     */
    public function getMaxBits(): array
    {
        $col = array_column(self::MAX_BITS, $this->getOrdinal());

        unset($col[0]); // remove the inavlid index 0

        return $col;
    }

    /**
     * Returns the maximum bit length for the given version and current ECC level
     */
    public function getMaxBitsForVersion(Version $version): int
    {
        return self::MAX_BITS[$version->getVersionNumber()][$this->getOrdinal()];
    }
}
